sharing\natives\windows/
windows.rs

1/*
2 * Authors: Jorge.A Duran & Mario Gónzalez
3 * Company: pispas Technologies SL
4 * Date: April 23, 2023
5 * Description: windows definition and implementation for singleton.
6 */
7use std::ffi::CString;
8
9#[allow(unused)]
10use winapi::um::synchapi::{CreateMutexA, ReleaseMutex, WaitForSingleObject};
11use winapi::um::winnt::HANDLE;
12use std::io::Write;
13use std::net::TcpStream;
14pub type MutexHandle = HANDLE;
15pub enum ResultCode {
16    Ok,
17    Abandoned = winapi::um::winbase::WAIT_ABANDONED as isize,
18}
19
20use std::process::{Command, Stdio};
21use std::thread::sleep;
22
23pub fn powershell_message_box_non_blocking(message: &str) {
24    let title = "UNPISPAS SERVICIO IMPRESION"; 
25    let command = format!(
26        "[reflection.assembly]::LoadWithPartialName('System.Windows.Forms')|out-null;[windows.forms.messagebox]::Show('{}', '{}')",
27        message, title
28    );
29
30    let result = Command::new("powershell")        
31        .arg("-WindowStyle")
32        .arg("Hidden")  
33        .arg("-Command")
34        .arg(command)
35        .stdout(Stdio::null()) 
36        .stderr(Stdio::piped())
37        .spawn(); 
38    
39    if let Err(e) = result {
40        tracing::error!("Error al ejecutar el comando PowerShell: {}", e);
41    }
42}
43
44pub fn create_mutex(name: &str) -> MutexHandle {
45    unsafe {
46        let mutex_name = CString::new(name).unwrap();
47        CreateMutexA(std::ptr::null_mut(), 1, mutex_name.as_ptr())
48    }
49}
50
51pub unsafe fn close_handle(handle: MutexHandle) {
52    unsafe {
53        winapi::um::handleapi::CloseHandle(handle);
54    }
55}
56
57pub fn send_messagebox_to_tray(message: String) -> crate::PisPasResult<()> {
58    match TcpStream::connect(crate::CHANNEL_NAME) {
59        Ok(mut stream) => {
60            // Aquí, pasamos el mensaje real
61            let action = crate::service::Action::MessageBox(message);
62            match stream.write_all(action.to_string().as_bytes()) {
63                Ok(_) => {
64                    tracing::info!("Message sent: {}", action.to_string());
65                    return Ok(());
66                }
67                Err(e) => {
68                    tracing::error!("Failed to write to socket in dedicated thread {}", e);
69                    return Err(anyhow::anyhow!("Failed to write to socket in dedicated thread {}", e));
70                }
71            }
72        }
73        Err(e) => {
74            tracing::error!("Error connecting to pispas-channel: {}", e);
75        }
76    }
77    Ok(())
78}
79
80pub fn stop_pispas_modules() -> crate::PisPasResult<()> {
81    match TcpStream::connect(crate::CHANNEL_NAME) {
82        Ok(mut stream) => {
83            match stream.write_all(crate::service::Action::Stop.to_string().as_bytes()) {
84                Ok(_) => {
85                    tracing::info!("Message sent: {}", crate::service::Action::Stop.to_string());
86                    return Ok(());
87                }
88                Err(e) => {
89                    tracing::error!("Failed to write to socket in dedicated thread {}", e);
90                    return Err(anyhow::anyhow!("Failed to write to socket in dedicated thread {}", e));
91                }
92            }
93        }
94        Err(e) => {
95            tracing::error!("Error connecting to pispas-channel: {}", e);            
96        }
97    }
98    sleep(std::time::Duration::from_secs(1));
99    Ok(())
100}
101
102pub unsafe fn wait_for_single_object(handle: MutexHandle, timeout: u32) -> ResultCode {
103    let code = unsafe { WaitForSingleObject(handle, timeout) };
104    if !(code == winapi::um::winbase::WAIT_ABANDONED || code == winapi::um::winbase::WAIT_OBJECT_0) {
105        ResultCode::Abandoned
106    } else {
107        ResultCode::Ok
108    }
109}
110
111pub unsafe fn release_mutex(handle: MutexHandle) {
112    unsafe { ReleaseMutex(handle) };
113}
114
115#[allow(non_camel_case_types, non_snake_case)]
116#[cfg(target_os = "windows")]
117pub mod native {
118    use std::{
119        ffi::CString,
120        ptr::null_mut,
121        thread::sleep,
122        time::Duration,
123    };
124    use widestring::WideCString;
125    use winapi::{
126        shared::minwindef::{BOOL, DWORD},
127        um::{
128            handleapi::CloseHandle,
129            processthreadsapi::{CreateProcessAsUserW, OpenProcess},
130            winbase::{CREATE_UNICODE_ENVIRONMENT},
131            winnt::{HANDLE, PROCESS_ALL_ACCESS, PROCESS_SUSPEND_RESUME},
132        },
133    };
134    use windows_sys::Win32::System::Environment::{
135        CreateEnvironmentBlock, DestroyEnvironmentBlock,
136    };
137    use windows_sys::Win32::Foundation::GetLastError;
138    use std::ptr;
139    use std::ffi::OsStr;
140
141    /// Windows `CREATE_NO_WINDOW` flag.
142    const CREATE_NO_WINDOW: u32 = 0x0800_0000;
143
144    /// Default upper bound for the legacy PowerShell helpers below.
145    /// Long enough to swallow a slow Get-WmiObject / Get-ChildItem
146    /// recursion on a sluggish disk, short enough that the installer
147    /// cannot freeze on a single child.
148    const POWERSHELL_TIMEOUT: Duration = Duration::from_secs(30);
149
150    /// Spawn `powershell` non-interactively, kill it after `timeout`,
151    /// drain stdout/stderr after exit. Same shape as installer's
152    /// `run_with_timeout` but localised here so the legacy callers in
153    /// this module (Get-WmiObject lookup, set_permissions_*) stop
154    /// hanging the installer when they get into a slow PowerShell
155    /// session — the kind of cuelgue we have been chasing.
156    ///
157    /// Always prepends `-NoProfile -NonInteractive -WindowStyle Hidden
158    /// -ExecutionPolicy Bypass` so callers don't need to repeat them
159    /// (and so we never accidentally leave one of them out, which is
160    /// what allowed Read-Host / profile loading to block in the past).
161    fn run_powershell_with_timeout(
162        args: &[&str],
163        timeout: Duration,
164    ) -> Result<std::process::Output, String> {
165        use std::io::Read;
166        use std::os::windows::process::CommandExt;
167
168        let mut full_args: Vec<String> = vec![
169            "-NoProfile".into(),
170            "-NonInteractive".into(),
171            "-WindowStyle".into(),
172            "Hidden".into(),
173            "-ExecutionPolicy".into(),
174            "Bypass".into(),
175        ];
176        full_args.extend(args.iter().map(|a| (*a).to_string()));
177
178        let mut child = std::process::Command::new("powershell")
179            .args(&full_args)
180            .stdin(std::process::Stdio::null())
181            .stdout(std::process::Stdio::piped())
182            .stderr(std::process::Stdio::piped())
183            .creation_flags(CREATE_NO_WINDOW)
184            .spawn()
185            .map_err(|e| format!("spawn powershell: {}", e))?;
186
187        // Drain stdout/stderr in dedicated reader threads. Without this,
188        // a noisy script (e.g. `set_permissions_robust` Write-Warning'ing
189        // every Get-ChildItem -Recurse iteration) fills the OS pipe buffer,
190        // PowerShell blocks on its next write, and we end up killing it on
191        // timeout even though it would have completed had we kept
192        // consuming output.
193        let stdout_handle = child.stdout.take().map(|mut s| {
194            std::thread::spawn(move || {
195                let mut buf = Vec::new();
196                let _ = s.read_to_end(&mut buf);
197                buf
198            })
199        });
200        let stderr_handle = child.stderr.take().map(|mut s| {
201            std::thread::spawn(move || {
202                let mut buf = Vec::new();
203                let _ = s.read_to_end(&mut buf);
204                buf
205            })
206        });
207
208        // Drain helper: join the reader threads. Once the child has been
209        // reaped (or killed) the pipes close, read_to_end returns and
210        // the threads finish — joining is the right way to (a) collect
211        // the buffered output and (b) not leak handles on every call.
212        // Used on every exit path below.
213        let collect_output = |stdout_h: Option<std::thread::JoinHandle<Vec<u8>>>,
214                              stderr_h: Option<std::thread::JoinHandle<Vec<u8>>>|
215         -> (Vec<u8>, Vec<u8>) {
216            let so = stdout_h.and_then(|h| h.join().ok()).unwrap_or_default();
217            let se = stderr_h.and_then(|h| h.join().ok()).unwrap_or_default();
218            (so, se)
219        };
220
221        let start = std::time::Instant::now();
222        let status = loop {
223            match child.try_wait() {
224                Ok(Some(s)) => break s,
225                Ok(None) => {
226                    if start.elapsed() > timeout {
227                        // Kill + reap so we don't leave an orphan
228                        // PowerShell, then drain readers so we don't
229                        // leak the JoinHandles either: dropping a
230                        // JoinHandle without joining lets those
231                        // threads keep running briefly and accumulate
232                        // across repeated timeouts.
233                        let _ = child.kill();
234                        let _ = child.wait();
235                        let _ = collect_output(stdout_handle, stderr_handle);
236                        return Err(format!("powershell exceeded {:?}, killed", timeout));
237                    }
238                    std::thread::sleep(Duration::from_millis(100));
239                }
240                Err(e) => {
241                    // try_wait error after spawn: the child might still
242                    // be running, so kill + reap explicitly to avoid
243                    // an orphan, and join readers before propagating
244                    // the error.
245                    let _ = child.kill();
246                    let _ = child.wait();
247                    let _ = collect_output(stdout_handle, stderr_handle);
248                    return Err(format!("wait powershell: {}", e));
249                }
250            }
251        };
252
253        // Happy path: process exited on its own. Pipes close, reader
254        // threads finish, we collect the buffered output.
255        let (stdout, stderr) = collect_output(stdout_handle, stderr_handle);
256
257        Ok(std::process::Output { status, stdout, stderr })
258    }
259    use std::os::windows::ffi::OsStrExt;
260    use winapi::um::winbase::WTSGetActiveConsoleSessionId;
261    use std::ffi::c_void; // Asegúrate de usar el tipo de `std::ffi`
262
263    const WTS_CURRENT_SERVER_HANDLE: HANDLE = null_mut();
264    const WTS_USER_NAME: DWORD = 5;
265
266    #[repr(C)]
267    struct WTS_SESSION_INFO {
268        SessionId: DWORD,
269        pWinStationName: *mut u16,
270        State: DWORD,
271    }
272
273    enum WTSSessionState {
274        WTSActive = 0,
275        WTSConnected = 1,
276        WTSDisconnected = 4,
277    }
278
279    #[link(name = "wtsapi32")]
280    extern "system" {
281        fn LoadLibraryA(lpFileName: *const i8) -> HANDLE;
282        fn GetProcAddress(hModule: HANDLE, lpProcName: *const i8) -> *mut c_void;
283        fn FreeLibrary(hLibModule: HANDLE) -> BOOL;
284        fn WTSQueryUserToken(SessionId: DWORD, phToken: *mut HANDLE) -> BOOL;
285        // fn WTSEnumerateSessionsW(hServer: HANDLE, Reserved: DWORD, Version: DWORD, ppSessionInfo: *mut *mut WTS_SESSION_INFO, pCount: *mut DWORD) -> BOOL;
286        fn WTSQuerySessionInformationW(hServer: HANDLE, SessionId: DWORD, WTSInfoClass: DWORD, ppBuffer: *mut *mut u16, pBytesReturned: *mut DWORD) -> BOOL;
287        fn WTSFreeMemory(pMemory: *mut c_void);
288    }
289
290    fn load_wtsapi32_module() -> Result<HANDLE, &'static str> {
291        let library_name = CString::new("wtsapi32.dll").expect("CString::new failed");
292        let h_module = unsafe { LoadLibraryA(library_name.as_ptr()) };
293        if h_module.is_null() {
294            Err("Failed to load wtsapi32.dll")
295        } else {
296            Ok(h_module)
297        }
298    }
299
300    fn get_function_addresses(h_module: HANDLE) -> Result<
301        (
302            extern "system" fn(HANDLE, DWORD, DWORD, *mut *mut WTS_SESSION_INFO, *mut DWORD) -> BOOL,
303            extern "system" fn(HANDLE, DWORD, DWORD, *mut *mut c_void, *mut DWORD) -> BOOL
304        ),
305        &'static str
306    > {
307        let function_name = CString::new("WTSEnumerateSessionsW").unwrap();
308        let enum_sessions: Option<extern "system" fn(HANDLE, DWORD, DWORD, *mut *mut WTS_SESSION_INFO, *mut DWORD) -> BOOL> = unsafe {
309            std::mem::transmute(GetProcAddress(h_module, function_name.as_ptr()))
310        };
311
312        let function_name_cstr = CString::new("WTSQuerySessionInformationW").unwrap();
313        let query_session: Option<extern "system" fn(HANDLE, DWORD, DWORD, *mut *mut c_void, *mut DWORD) -> BOOL> = unsafe {
314            std::mem::transmute(GetProcAddress(h_module, function_name_cstr.as_ptr()))
315        };
316
317        match (enum_sessions, query_session) {
318            (Some(enumerate_sessions_func), Some(query_session_func)) => Ok((enumerate_sessions_func, query_session_func)),
319            _ => Err("Failed to get procedure addresses.")
320        }
321    }
322
323    fn to_wide_cstring(value: &str) -> WideCString {
324        WideCString::from_str(value).unwrap()
325    }
326
327    fn create_command_line_wide(executable_path: &str, script_path: &str) -> WideCString {
328        let cmd = format!("\"{}\" \"{}\"", executable_path.trim_end_matches('\\'), script_path);
329        WideCString::from_str(&cmd).unwrap()
330    }
331
332    fn wait_for_user_session(session_id: DWORD) -> anyhow::Result<()> {
333        loop {
334            let state = query_session_state(session_id)?;
335            if state == WTSSessionState::WTSActive as DWORD {
336                return Ok(()); // WTSActive
337            }
338            sleep(Duration::from_millis(500));
339        }
340    }
341
342    fn query_session_state(session_id: DWORD) -> anyhow::Result<DWORD> {
343        let mut p_buffer: *mut u16 = null_mut();
344        let mut bytes_returned: DWORD = 0;
345
346        unsafe {
347            let success = WTSQuerySessionInformationW(
348                WTS_CURRENT_SERVER_HANDLE,
349                session_id,
350                0, // WTSConnectState
351                &mut p_buffer,
352                &mut bytes_returned,
353            );
354
355            if success == 0 {
356                return Err(anyhow::anyhow!("Failed to query session information"));
357            }
358
359            let state = *p_buffer as DWORD;
360            WTSFreeMemory(p_buffer as *mut c_void);
361            Ok(state)
362        }
363    }
364
365    fn wait_for_any_active_session(enumerate_sessions_func: extern "system" fn(HANDLE, DWORD, DWORD, *mut *mut WTS_SESSION_INFO, *mut DWORD) -> BOOL) -> anyhow::Result<()> {
366        loop {
367            let mut session_info_ptr: *mut WTS_SESSION_INFO = null_mut();
368            let mut session_count: DWORD = 0;
369
370            let success = enumerate_sessions_func(
371                WTS_CURRENT_SERVER_HANDLE,
372                0,
373                1,
374                &mut session_info_ptr,
375                &mut session_count,
376            );
377
378            if success != 0 {
379                unsafe {
380                    for i in 0..session_count {
381                        let session = session_info_ptr.offset(i as isize);
382
383                        if (*session).State == WTSSessionState::WTSActive as u32 {
384                            return Ok(()); // Found an active session
385                        }
386                    }
387                }
388            }
389            sleep(Duration::from_secs(1));
390        }
391    }
392
393    fn launch_process_for_user(executable_path: &str, command_line: &str, session_id: DWORD) -> Option<HANDLE> {
394        unsafe {
395            if let Err(e) = wait_for_user_session(session_id) {
396                tracing::error!("Failed to wait for user session: {}", e);
397                return None;
398            }
399
400            // Añadimos un sleep adicional para asegurar que la sesión esté completamente inicializada
401            sleep(Duration::from_secs(1));
402
403            let mut user_token: HANDLE = null_mut();
404            if WTSQueryUserToken(session_id, &mut user_token) != 0 {
405                let exe_path_wide = to_wide_cstring(executable_path);
406                let cmd_line_wide = create_command_line_wide(executable_path, command_line);
407                let path_current_dir_wide = to_wide_cstring(&crate::paths::bin_dir().display().to_string());
408
409                tracing::info!("Executable path: {}", executable_path);
410                tracing::info!("Command line: {:?}", cmd_line_wide);
411
412                let mut startup_info = std::mem::zeroed::<winapi::um::processthreadsapi::STARTUPINFOW>();
413                startup_info.cb = std::mem::size_of::<winapi::um::processthreadsapi::STARTUPINFOW>() as u32;
414
415                let desktop_name_wide: Box<Vec<u16>> = Box::new(to_wide_string("WinSta0\\Default"));
416                startup_info.lpDesktop = desktop_name_wide.as_ptr() as *mut u16;
417                                
418                let mut environment_block: *mut c_void = std::ptr::null_mut();
419                // create the environment block for the user
420                if CreateEnvironmentBlock(&mut environment_block, user_token as *mut c_void, 1)  == 0 {
421                    let error_code = GetLastError();
422                    tracing::error!("Failed to create environment block for user. Error code: {}", error_code);
423                    CloseHandle(user_token);
424                    return None;
425                }
426                
427                let _keep_alive = desktop_name_wide; // Esto mantiene la memoria durante el uso
428
429                let mut process_info = std::mem::zeroed::<winapi::um::processthreadsapi::PROCESS_INFORMATION>();
430                tracing::info!("exe_path: {:?}, command_line: {:?}, path_current_dir: {:?}", cmd_line_wide, cmd_line_wide, path_current_dir_wide);
431                
432            
433                let success = CreateProcessAsUserW(
434                    user_token,
435                    exe_path_wide.as_ptr() as *mut u16,
436                    cmd_line_wide.as_ptr() as *mut u16,
437                    null_mut(),
438                    null_mut(),
439                    0,
440                    CREATE_UNICODE_ENVIRONMENT,
441                    environment_block as *mut winapi::ctypes::c_void,
442                    path_current_dir_wide.as_ptr() as *mut u16,
443                    &mut startup_info,
444                    &mut process_info,
445                );
446
447                if success != 0 {
448                    CloseHandle(user_token);
449                    DestroyEnvironmentBlock(environment_block);
450                    return Some(process_info.hProcess);
451                } else {
452                    let error_code = GetLastError();
453                    tracing::error!("CreateProcessAsUserW failed: Error code = {}", error_code);
454                }
455                DestroyEnvironmentBlock(environment_block);
456                CloseHandle(user_token);
457            } else {
458                let error_code = GetLastError();
459                tracing::error!("WTSQueryUserToken failed for session {}: Error code = {}", session_id, error_code);
460            }
461        }
462
463        None
464    }
465
466    fn enumerate_and_launch(
467        executable_path: &str,
468        command_line: &str,
469        all_users: bool,
470        first_execution: bool,
471        enumerate_sessions_func: extern "system" fn(HANDLE, DWORD, DWORD, *mut *mut WTS_SESSION_INFO, *mut DWORD) -> BOOL,
472        query_session_func: extern "system" fn(HANDLE, DWORD, DWORD, *mut *mut c_void, *mut DWORD) -> BOOL
473    ) -> crate::PisPasResult<Option<HANDLE>> {
474        wait_for_any_active_session(enumerate_sessions_func)?;
475
476        let mut session_info_ptr: *mut WTS_SESSION_INFO = null_mut();
477        let mut session_count: DWORD = 0;
478
479        let success = enumerate_sessions_func(
480            WTS_CURRENT_SERVER_HANDLE,
481            0,
482            1,
483            &mut session_info_ptr,
484            &mut session_count,
485        );
486
487        if success != 0 {
488            tracing::info!("Found {} sessions.", session_count);
489            unsafe {
490                for i in 0..session_count {
491                    if session_info_ptr.is_null() {
492                        return Err(anyhow::anyhow!("Session info pointer is null"));
493                    }
494                    let session = session_info_ptr.offset(i as isize);
495
496                    if (*session).State == WTSSessionState::WTSActive as u32 ||
497                        (*session).State == WTSSessionState::WTSConnected as u32 ||
498                        (*session).State == WTSSessionState::WTSDisconnected as u32 {
499                        let mut data: *mut c_void = null_mut();
500                        let mut bytes: DWORD = 0;
501
502                        if query_session_func(WTS_CURRENT_SERVER_HANDLE, (*session).SessionId, WTS_USER_NAME, &mut data, &mut bytes) != 0 {
503                            let user_name: *const u16 = data as *const u16;
504                            let user_slice = std::slice::from_raw_parts(user_name, bytes as usize / 2);
505                            match String::from_utf16(user_slice) {
506                                Ok(mut user) => {
507                                    user = user.trim_end_matches('\0').to_string(); // Remove null characters
508                                    if !user.trim().is_empty() && (*session).SessionId != 0 {
509                                        tracing::info!("Session {}: User = {}", (*session).SessionId, user);
510                                        let bin_dir = crate::paths::bin_dir().display().to_string();
511                                        tracing::info!("bin_dir: {}", bin_dir);
512                                        if first_execution {
513                                            if let Err(e) = set_permissions_robust(&bin_dir, &user) {
514                                                tracing::error!("Failed to set permissions for user {}: {}", user, e);
515                                            } else {
516                                                tracing::info!("Permissions set for user {}", user);
517                                                sleep(Duration::from_secs(1));
518                                            }
519                                        }
520
521                                        if let Some(process_handle) = launch_process_for_user(executable_path, command_line, (*session).SessionId) {
522                                            tracing::info!("Process launched for user {}", user);
523                                            if !all_users {
524                                                return Ok(Some(process_handle));
525                                            }
526                                        } else {
527                                            tracing::error!("Failed to launch process for user {}", user);
528                                        }
529                                    } else {
530                                        tracing::warn!("Ignoring session {}: User name is empty or session ID is 0", (*session).SessionId);
531                                    }
532                                }
533                                Err(e) => {
534                                    tracing::info!("Failed to convert UTF-16 to string: {}", e);
535                                    return Err(anyhow::anyhow!("Error converting UTF-16 to string"));
536                                }
537                            }
538                        }
539                    }
540                }
541            }
542        } else {
543            return Err(anyhow::anyhow!("Error enumerate_sessions_func"));
544        }
545        Ok(None)
546    }
547    // Function to convert &str to Vec<u16> for Windows compatibility
548    fn to_wide_string(s: &str) -> Vec<u16> {
549        OsStr::new(s).encode_wide().chain(Some(0)).collect()
550    }
551    struct WideString {
552        inner: Vec<u16>,
553    }
554
555    impl WideString {
556        pub fn new(s: &str) -> Self {
557            Self {
558                inner: to_wide_string(s),
559            }
560        }
561
562        pub fn as_ptr(&self) -> *const u16 {
563            self.inner.as_ptr()
564        }
565    }
566    // other way to launch poss as logged in user
567    pub fn launch_process_as_logged_in_user(executable_path: &str, command_line: &str, first_launch: bool) -> crate::PisPasResult<Option<HANDLE>> {
568        // Outer cap so a consistently-hanging WMI session cannot keep us
569        // here for ~20 attempts × 30s each (≈ 10 minutes), which would
570        // look exactly like an "update colgado" to the user. We also
571        // stop retrying as soon as a single attempt times out: if WMI
572        // hung once it is going to hang the next time too, retrying
573        // just wastes the user's life.
574        const TOTAL_LOGGED_USER_BUDGET: Duration = Duration::from_secs(60);
575        let loop_started = std::time::Instant::now();
576
577        let mut attempts = 0;
578        let user = loop {
579            if attempts >= 20 {
580                return Err(anyhow::anyhow!("No user found logged in after multiple attempts."));
581            }
582            // Cap each attempt by whatever budget we have left. Without
583            // this an attempt that runs the full POWERSHELL_TIMEOUT
584            // could push the loop's total elapsed time well past the
585            // 60s budget (we only checked the budget BEFORE starting
586            // the attempt).
587            let budget_remaining = TOTAL_LOGGED_USER_BUDGET
588                .checked_sub(loop_started.elapsed())
589                .unwrap_or_default();
590            if budget_remaining.is_zero() {
591                return Err(anyhow::anyhow!(
592                    "No user found logged in within {:?} budget — giving up to avoid update hang.",
593                    TOTAL_LOGGED_USER_BUDGET
594                ));
595            }
596            let attempt_timeout = std::cmp::min(POWERSHELL_TIMEOUT, budget_remaining);
597
598            // Wrapped in run_powershell_with_timeout so a slow / hanging
599            // WMI subsystem cannot freeze the installer (we used to spin
600            // here for minutes when WMI was corrupt).
601            let result = run_powershell_with_timeout(
602                &["-Command", "(Get-WmiObject -Class Win32_ComputerSystem).UserName"],
603                attempt_timeout,
604            );
605
606            match result {
607                Ok(output) if output.status.success() => {
608                    let user = String::from_utf8_lossy(&output.stdout).trim().to_string();
609                    if !user.is_empty() {
610                        break user;
611                    } else {
612                        tracing::error!("No user found logged in, retrying...");
613                    }
614                }
615                Ok(output) => {
616                    let stderr = String::from_utf8_lossy(&output.stderr);
617                    tracing::error!("Error getting logged in user: {}, retrying...", stderr);
618                }
619                Err(e) => {
620                    // Helper failed (spawn / wait / try_wait error, or
621                    // killed on timeout). Bail immediately: if it is a
622                    // timeout we know WMI is hanging and retrying is
623                    // pointless; if it is a spawn or wait error the
624                    // problem is environmental and not going to clear
625                    // itself in a 1-second sleep.
626                    return Err(anyhow::anyhow!(
627                        "powershell get-user failed, aborting: {}",
628                        e
629                    ));
630                }
631            }
632
633            attempts += 1;
634            sleep(Duration::from_secs(1));
635        };
636
637
638        tracing::info!("Logged in user: {}", user);
639        let username = user.split('\\').last().unwrap_or(&user);
640        tracing::info!("Extracted username: {}", username);
641        
642        //adjust permissions for the user on the specified executable
643        if first_launch{
644            if let Err(e) = set_permissions_robust(executable_path, &username) {
645                tracing::error!("Failed to set permissions for user {}: {}", user, e);
646            } else {
647                tracing::info!("Permissions set for user {}", user);
648                sleep(Duration::from_secs(1));
649            }
650        }        
651        
652        // obtain the active session ID and user token
653        let session_id = unsafe { WTSGetActiveConsoleSessionId() };
654        if session_id == u32::MAX {
655            let error_code = unsafe { GetLastError() };
656            tracing::error!("Failed to get active session ID: {}", error_code);
657            return Err(anyhow::anyhow!("Failed to get active session ID: {}", error_code));
658        }
659
660        let mut user_token: HANDLE = ptr::null_mut();
661        if unsafe { WTSQueryUserToken(session_id, &mut user_token) } == 0 {
662            let error_code = unsafe { GetLastError() };
663            tracing::error!("Failed to get user token: {}", error_code);
664            return Err(anyhow::anyhow!("Failed to get user token: {}", error_code));
665        }
666        
667        let full_command = format!("\"{}\" \"{}\"", executable_path, command_line);
668        let command_utf16 = OsStr::new(&full_command).encode_wide().chain(Some(0)).collect::<Vec<u16>>();
669
670        if first_launch {
671            sleep(Duration::from_secs(4));    
672        }        
673        let mut startup_info = unsafe { std::mem::zeroed::<winapi::um::processthreadsapi::STARTUPINFOW>() };
674        startup_info.cb = std::mem::size_of::<winapi::um::processthreadsapi::STARTUPINFOW>() as u32;
675        let desktop_name = WideString::new("WinSta0\\Default");
676        startup_info.lpDesktop = desktop_name.as_ptr() as *mut u16;
677
678        // Structure to receive process information
679        let mut process_info = unsafe { std::mem::zeroed::<winapi::um::processthreadsapi::PROCESS_INFORMATION>() };
680
681        // Lanza el proceso en el contexto del usuario logeado
682        let success = unsafe {
683            CreateProcessAsUserW(
684                user_token,
685                ptr::null(),
686                command_utf16.as_ptr() as *mut _,
687                ptr::null_mut(),
688                ptr::null_mut(),
689                0,
690                CREATE_UNICODE_ENVIRONMENT,
691                ptr::null_mut(),
692                ptr::null(),
693                &mut startup_info,
694                &mut process_info,
695            )
696        };
697        
698        unsafe { CloseHandle(user_token) };
699
700        if success == 0 {
701            let error_code = unsafe { GetLastError() };
702            tracing::error!("Failed to launch process: {}", error_code);
703            Err(anyhow::anyhow!("Failed to launch process: {}", error_code))
704        } else {
705            tracing::info!("Process launched successfully in the context of the logged-in user.");
706            Ok(Some(process_info.hProcess))
707        }
708    }
709    
710    pub fn run_in_all_sessions(executable_path: &str, command_line: &str, all_user: bool, first_execution: bool) -> crate::PisPasResult<Option<HANDLE>> {
711        match load_wtsapi32_module() {
712            Ok(h_module) => {
713                match get_function_addresses(h_module) {
714                    Ok((enumerate_sessions_func, query_session_func)) => {
715                        tracing::info!("Enumerating sessions...");
716                        let result = enumerate_and_launch(executable_path, command_line, all_user, first_execution, enumerate_sessions_func, query_session_func);
717                        unsafe { FreeLibrary(h_module); }
718                        result
719                    }
720                    Err(e) => {
721                        unsafe { FreeLibrary(h_module); }
722                        tracing::error!("{}", e);
723                        Err(anyhow::anyhow!("Error getting function addresses"))
724                    }
725                }
726            }
727            Err(e) => {
728                tracing::error!("error {}", e);
729                Err(anyhow::anyhow!("Error loading wtsapi32 module"))
730            }
731        }
732    }
733
734    pub fn open_process(pid: u32) -> Option<HANDLE> {
735        let desired_access = PROCESS_ALL_ACCESS | PROCESS_SUSPEND_RESUME;
736        let process_handle = unsafe { OpenProcess(desired_access, 0, pid) };
737
738        if process_handle.is_null() {
739            unsafe {
740                tracing::info!("Error obtaining process handle. Error: {}", GetLastError());
741            }
742            None
743        } else {
744            tracing::info!("Process handle: {:?}", process_handle);
745            Some(process_handle)
746        }
747    }
748
749    /// Hybrid approach - use PowerShell with better error handling
750    pub fn set_permissions_robust(path: &str, user: &str) -> Result<(), String> {
751        let user = user.trim_end_matches('\0');
752        let path = path.trim_end_matches('\0');
753
754        let powershell_script = format!(
755            r#"
756        try {{
757            $path = '{}'
758            $user = '{}'
759            $successCount = 0
760            $errorCount = 0
761            
762            # Set permissions on root folder first
763            try {{
764                $acl = Get-Acl $path
765                $rule = New-Object System.Security.AccessControl.FileSystemAccessRule($user,'FullControl','ContainerInherit,ObjectInherit','None','Allow')
766                $acl.SetAccessRule($rule)
767                Set-Acl $path $acl
768                $successCount++
769                Write-Output "Root permissions set: $path"
770            }} catch {{
771                $errorCount++
772                Write-Warning "Failed to set root permissions: $($_.Exception.Message)"
773            }}
774            
775            # Process all child items
776            Get-ChildItem -Path $path -Recurse -Force -ErrorAction SilentlyContinue | ForEach-Object {{
777                try {{
778                    $acl = Get-Acl $_.FullName
779                    $rule = New-Object System.Security.AccessControl.FileSystemAccessRule($user,'FullControl','ContainerInherit,ObjectInherit','None','Allow')
780                    $acl.SetAccessRule($rule)
781                    Set-Acl $_.FullName $acl
782                    $successCount++
783                }} catch {{
784                    $errorCount++
785                    Write-Warning "Failed: $($_.FullName) - $($_.Exception.Message)"
786                }}
787            }}
788            
789            Write-Output "Completed: $successCount successful, $errorCount errors"
790        }} catch {{
791            Write-Error $_.Exception.Message
792            exit 1
793        }}
794        "#,
795            path, user
796        );
797
798        tracing::info!("Executing robust recursive permissions for: {}", path);
799
800        // 60s instead of the default 30s: Get-ChildItem -Recurse on a
801        // big bin dir on a slow disk can legitimately take a while.
802        // Anything beyond that is a real cuelgue and we kill it.
803        let output = match run_powershell_with_timeout(
804            &["-Command", &powershell_script],
805            Duration::from_secs(60),
806        ) {
807            Ok(o) => o,
808            Err(e) => return Err(format!("set_permissions_robust powershell failed: {}", e)),
809        };
810
811        let stdout = String::from_utf8_lossy(&output.stdout).to_string();
812        let stderr = String::from_utf8_lossy(&output.stderr).to_string();
813
814        tracing::info!("robust_permissions stdout: {}", stdout);
815        if !stderr.is_empty() {
816            tracing::warn!("robust_permissions stderr: {}", stderr);
817        }
818
819        if !output.status.success() {
820            return Err(format!("Failed to set robust permissions: {}", stderr));
821        }
822
823        Ok(())
824    }
825    pub fn set_permissions_for_user(path: &str, user: &str) -> Result<(), String> {
826        let user = user.trim_end_matches('\0');
827        let path = path.trim_end_matches('\0');
828        // `exit 1` in the catch is what makes `output.status.success()`
829        // return false on the Rust side. Without it, `Write-Error` alone
830        // does not set a non-zero exit code, so a failed Set-Acl would
831        // silently report success up to the caller. The robust variant
832        // already does this; keeping both consistent.
833        let powershell_script = format!(
834            r#"
835        try {{
836            $path = '{}'
837            $user = '{}'
838            $acl = Get-Acl $path
839            $rule = New-Object System.Security.AccessControl.FileSystemAccessRule($user,'FullControl','None','None','Allow')
840            $acl.SetAccessRule($rule)
841            Set-Acl $path $acl
842            Write-Output "Permissions set successfully"
843        }} catch {{
844            Write-Error $_.Exception.Message
845            exit 1
846        }}
847        "#,
848            path, user
849        );
850
851        tracing::info!("powershell_script: {:?}", powershell_script);
852
853        let output = match run_powershell_with_timeout(
854            &["-Command", &powershell_script],
855            POWERSHELL_TIMEOUT,
856        ) {
857            Ok(o) => o,
858            Err(e) => return Err(format!("set_permissions_for_user powershell failed: {}", e)),
859        };
860
861        let stdout = String::from_utf8_lossy(&output.stdout).to_string();
862        let stderr = String::from_utf8_lossy(&output.stderr).to_string();
863
864        tracing::info!("set_permissions_for_user stdout: {}", stdout);
865        tracing::info!("set_permissions_for_user stderr: {}", stderr);
866
867        if !output.status.success() {
868            return Err(format!("Failed to set permissions: {}", stderr));
869        }
870
871        Ok(())
872    }
873}
874
875pub fn get_name_service() -> String {
876    let path = match std::env::current_exe() {
877        Ok(path) => path,
878        Err(e) => {
879            println!("Error al obtener el nombre del archivo ejecutable: {:?}", e);
880            return "ServiceDefault".to_string();
881        }
882    };
883    let name = match path.file_name() {
884        Some(name_without_extension) => name_without_extension.to_str().unwrap(),
885        None => {
886            println!("Error al obtener el nombre del archivo ejecutable");
887            return "ServiceDefault".to_string();
888        }
889    };
890    let _name_without_extension = name.replace(".exe", "");
891    _name_without_extension.to_string()
892}